﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PetriNetSharp.Engine;
using System.Xml.Linq;
using System.Diagnostics;
using System.Windows;

namespace PetriNetSharp
{
	/// <summary>
	/// This class is responsible for saving and loading data
	/// </summary>
	public class IOSystem
	{
		#region public constants

		/// <summary>
		/// The file extenstion for save files
		/// </summary>
		public const string FILE_EXTENSTION = "*pns";

		/// <summary>
		/// The file extension filter for save file dialogs
		/// </summary>
		public const string FILE_SAVE_FILTER = "PetriNetSharp Graphs|*.pns|All Files|*.*";

		#endregion

		#region node name constants

		/// <summary>
		/// The name of the root element
		/// </summary>
		private const string ROOT_NAME = "PetriNetGraph";

		/// <summary>
		/// The name of the Place elements
		/// </summary>
		private const string PLACE_NAME = "Place";

		/// <summary>
		/// The name of the Transition elements
		/// </summary>
		private const string TRANSITION_NAME = "Transition";

		/// <summary>
		/// The name of the inputs elements
		/// </summary>
		private const string INPUT_NAME = "Inputs";

		/// <summary>
		/// The nameof the input place elements
		/// </summary>
		private const string INPUT_PLACE_NAME = "InputPlace";

		/// <summary>
		/// The name of the outputs elements
		/// </summary>
		private const string OUTPUT_NAME = "Outputs";

		/// <summary>
		/// The name of the output place elements
		/// </summary>
		private const string OUTPUT_PLACE_NAME = "OutputPlace";

		/// <summary>
		/// The name of the Arc elements
		/// </summary>
		private const string ARC_NAME = "Arc";

		/// <summary>
		/// The string for the name attribute name
		/// </summary>
		private const string NAME_ATTRIBUTE= "Name";

		/// <summary>
		/// The string for the location attribute name
		/// </summary>
		private const string LOCATION_ATTRIBUTE = "Location";

		/// <summary>
		/// The string for the UID attribute name
		/// </summary>
		private const string UID_ATTRIBUTE = "UID";

		/// <summary>
		/// The string for the token attribute name
		/// </summary>
		private const string TOKEN_ATTRIBUTE = "Tokens";

		/// <summary>
		/// The string for the max tokens attribute name
		/// </summary>
		private const string MAX_TOKEN_ATTRIBUTE = "MaxTokens";

		/// <summary>
		/// The string for the tokens to transfer attribute name
		/// </summary>
		private const string TOKENS_TO_TRANSFER = "TokensToTransfer";

		/// <summary>
		/// The string for the start attribute
		/// </summary>
		private const string START_ATTIBUTE = "Start";

		/// <summary>
		/// The string for the end attribute name
		/// </summary>
		private const string END_ATTRIBUTE = "End";

		#endregion

		/// <summary>
		/// Occurs when the current file changes
		/// </summary>
		public event Action CurrentFileChanged;

		/// <summary>
		/// The current file being used
		/// </summary>
		private string currentFile;

		/// <summary>
		/// Create a new IOSystem
		/// </summary>
		public IOSystem()
		{
			currentFile = "";
		}

		/// <summary>
		/// Get whether or not the IOSytem has a current file it is working with
		/// </summary>
		public bool HasAFile
			{ get { return !string.IsNullOrEmpty(currentFile); } }

		/// <summary>
		/// Get the current file being worked with
		/// </summary>
		/// <remarks>
		/// This is set through the save as and load methods
		/// </remarks>
		public string CurrentFile
		{
			get { return currentFile; }
			private set 
			{ 
				currentFile = value;
				if (CurrentFileChanged != null)
					CurrentFileChanged();
			}
		}

		/// <summary>
		/// Save the PetriNetGraph
		/// </summary>
		/// <param name="graph">The PetriNetGraph to save</param>
		public void Save(PetriNetGraph graph)
		{
			SaveAs(graph, CurrentFile);
		}

		/// <summary>
		/// Save the PetriNetGraph to a file other than the current file
		/// </summary>
		/// <remarks>
		/// The passed file then becomes the current file
		/// </remarks>
		/// <param name="graph">The PetriNetGraph to save</param>
		/// <param name="file">The file location</param>
		/// <preconditions>
		/// graph cannot be null
		/// file cannot be null or ""
		/// </preconditions>
		public void SaveAs(PetriNetGraph graph, string file)
		{
			#region preconditions
			Debug.Assert(graph != null, "SaveAs called with graph as null",
				"SaveAs in IOSystem was called when the parameter: graph was null");
			Debug.Assert(!string.IsNullOrEmpty(file), "SaveAs called withi file null or \"\" ",
				"Saveas in IOSystem was called when the parameter: file was null or \"\" ");
			#endregion
			XDocument document = new XDocument();
			XComment comment = new XComment(string.Format("Saved on: {0} {1}", 
				DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString()));
			document.Add(comment);
			XElement root = new XElement(ROOT_NAME);
			document.Add(root);

			XElement node;

			//Save Each Place
			foreach(Place place in graph.Places)
			{
				node = new XElement(PLACE_NAME,
					new XAttribute(UID_ATTRIBUTE, place.UID),
					new XAttribute(NAME_ATTRIBUTE, place.Name),
					new XAttribute(LOCATION_ATTRIBUTE, place.Location.ToString()),
					new XAttribute(TOKEN_ATTRIBUTE, place.StartingTokens.ToString()),
					new XAttribute(MAX_TOKEN_ATTRIBUTE, place.MaxTokens.ToString()));
				root.Add(node);
			}

			//Save each Transition
			foreach(Transition trans in graph.Transitions)
			{
				node = new XElement(TRANSITION_NAME,
					new XAttribute(UID_ATTRIBUTE, trans.UID),
					new XAttribute(NAME_ATTRIBUTE, trans.Name),
					new XAttribute(LOCATION_ATTRIBUTE, trans.Location.ToString()),
					new XAttribute(TOKENS_TO_TRANSFER, trans.TokensToTransfer.ToString()));

				root.Add(node);
			}

			//Save each Arc
			foreach(Arc arc in graph.Arcs)
			{
				node = new XElement(ARC_NAME,
					new XAttribute(START_ATTIBUTE, arc.Start.UID),
					new XAttribute(END_ATTRIBUTE, arc.End.UID));

				root.Add(node);
			}

			document.Save(file);

			CurrentFile = file;
		}

		/// <summary>
		/// Load the PetriNet located in a file
		/// </summary>
		/// <param name="file">The file containing the petri net graph</param>
		/// <returns>The PetriNetGraph Loaded</returns>
		public PetriNetGraph Load(string file)
		{
			PetriNetGraph graph = new PetriNetGraph();

			XDocument loadedDocument = XDocument.Load(file);

			//Read the place elements
			var placeElements =
				from place in loadedDocument.Root.Elements()
				where place.Name.LocalName.Equals(PLACE_NAME)
				select place;

			//Create and add the places
			Place tempPlace;
			foreach (XElement placeElement in placeElements)
			{
				
				tempPlace = new Place(
					Point.Parse(placeElement.Attribute(LOCATION_ATTRIBUTE).Value),
					placeElement.Attribute(NAME_ATTRIBUTE).Value, 
					placeElement.Attribute(UID_ATTRIBUTE).Value, 
					int.Parse(placeElement.Attribute(TOKEN_ATTRIBUTE).Value),
					int.Parse(placeElement.Attribute(MAX_TOKEN_ATTRIBUTE).Value));

				graph.AddPlace(tempPlace);
			}

			//Read the transition elements
			var transitionElements =
				from transition in loadedDocument.Root.Elements()
				where transition.Name.LocalName.Equals(TRANSITION_NAME)
				select transition;

			//Create and add the transitions
			Transition tempTrans;
			foreach (XElement transitionElement in transitionElements)
			{

				tempTrans = new Transition(transitionElement.Attribute(NAME_ATTRIBUTE).Value,
					Point.Parse(transitionElement.Attribute(LOCATION_ATTRIBUTE).Value),
					transitionElement.Attribute(UID_ATTRIBUTE).Value,
					int.Parse(transitionElement.Attribute(TOKENS_TO_TRANSFER).Value));
				graph.AddTransition(tempTrans);
			}

			//Read the Arc elements
			var arcElements =
				from arc in loadedDocument.Root.Elements()
				where arc.Name.LocalName.Equals(ARC_NAME)
				select arc;

			Arc tempArc;
			PetriObject start;
			PetriObject end;
			XAttribute UIDAttribute;
			foreach (XElement arcElement in arcElements)
			{
				start = null;
				end = null;

				UIDAttribute = arcElement.Attribute(START_ATTIBUTE);

				if (PetriNetGraph.IsPlaceUID(UIDAttribute.Value))
					start = graph.GetPlace(UIDAttribute.Value);
				else if (PetriNetGraph.IsTransitionUID(UIDAttribute.Value))
					start = graph.GetTransition(UIDAttribute.Value);

				#region assert
				Debug.Assert(start != null, "While loading the start UID for an Arc the UID was not found",
					String.Format("The start UID for an arc was not found, the UID was: {0}", UIDAttribute.Value));
				#endregion

				UIDAttribute = arcElement.Attribute(END_ATTRIBUTE);
				if(PetriNetGraph.IsPlaceUID(UIDAttribute.Value))
					end = graph.GetPlace(UIDAttribute.Value);
				else if(PetriNetGraph.IsTransitionUID(UIDAttribute.Value))
					end = graph.GetTransition(UIDAttribute.Value);

				#region assert
				Debug.Assert(start != null, "While loading the end UID for an Arc the UID was not found",
					String.Format("The end UID for an arc was not found, the UID was: {0}", UIDAttribute.Value));
				#endregion

				tempArc = new Arc(start, end);

				graph.AddArc(tempArc);
			}

			CurrentFile = file;
			return graph;
			
		}
	}
}
